package com.rankeiot.platform.controller;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.rankeiot.core.Current;
import com.rankeiot.core.anno.OperationLog;
import com.rankeiot.core.data.LocalizedMessage;
import com.rankeiot.core.data.UserInfo;
import com.rankeiot.core.data.response.Resp;
import com.rankeiot.core.util.Captcha;
import com.rankeiot.core.util.JSONUtil;
import com.rankeiot.core.util.StringUtil;
import com.rankeiot.core.util.TokenUtil;
import com.rankeiot.platform.config.LoginConfig;
import com.rankeiot.platform.config.PlatformConfig;
import com.rankeiot.platform.config.SmsConfigs;
import com.rankeiot.platform.domain.User;
import com.rankeiot.platform.domain.WxUser;
import com.rankeiot.platform.service.SmsService;
import com.rankeiot.platform.service.UserService;
import com.rankeiot.platform.util.LocalCacheUtil;
import com.rankeiot.platform.util.LockStatusBL;
import com.rankeiot.platform.util.PasswordUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.sagacity.sqltoy.dao.SqlToyLazyDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.*;

@Tag(name="系统登录接口")
@RestController
@RequestMapping("/systemLogin")
@RequiredArgsConstructor
public class SystemLoginController {
    final SqlToyLazyDao dao;
    private SmsService smsService;
    final UserService userService;
    private static long ExpireMINUTE = 1;
    private long ExpireTime = ExpireMINUTE * DateUnit.MINUTE.getMillis();//过期时间
    private static String LOGIN_SESSIONKEY = "_Captcha_ExpireTime_Login";
    private static String REGISTER_SESSIONKEY = "_Captcha_ExpireTime_Register";
    private static String FORGOT_SESSIONKEY = "_Captcha_ExpireTime_Forget";
    private static String WXBIND_SESSIONKEY = "_Captcha_ExpireTime_WxBind";
    public static String WXINFO_SESSIONKEY = "_Captcha_WxInfo";

    @Autowired(required = false)
    public void setSmsService(SmsService smsService){
        this.smsService=smsService;
    }

    @Operation(summary = "发送注册账号短信")
    @GetMapping("/sms/sendSmsForRegister")
    public Resp<Void> sendSmsForRegister(HttpServletRequest request,@RequestParam String phoneNumber,@RequestParam String verify,@CookieValue(value=Captcha.captchaCookieName,required = false) String vid) {
        validCapcha(verify,vid);
        validIsSend(request,REGISTER_SESSIONKEY,phoneNumber);
        //手机号参数校验
        boolean mobile = Validator.isMobile(phoneNumber);
        if (!mobile) {
            Resp.fail(Messages.PhoneIncorrect);
        }
        UserInfo user = new UserInfo();
        user.setUsername(phoneNumber);
        user = dao.loadBySql("select * from t_user where username=:username",user);
        if(ObjectUtil.isNotEmpty(user)){
            Resp.fail(Messages.PhoneExist);
        }
        String templateCode = SmsConfigs.registerTemplate.strValue();
        String sign = SmsConfigs.captchaSin.strValue();
        String randomNumber = RandomUtil.randomNumbers(6);
        Map<String,Object> params = new HashMap<>();
        params.put("code",randomNumber);
        smsService.sendMessage(sign,phoneNumber,templateCode,params);
        //缓存
        setCache(request,randomNumber,REGISTER_SESSIONKEY,phoneNumber);
        return Resp.ok();
    }
    @Operation(summary = "注册")
    @PostMapping("register")
    public Resp register(HttpServletRequest request,@Valid @RequestBody ForgetPwDto dto) {
        validCode(request,REGISTER_SESSIONKEY,dto.getPhone(),dto.getCode());
        if (!Objects.equals(dto.getConfirmPassword(), dto.getPassword())) {
            Resp.fail(Messages.PasswordInconsistent);
        }
        User user = new User();
        String sql = "SELECT * FROM t_user WHERE username = '"+dto.getPhone()+"'";
        List<User> bySql = dao.findBySql(sql, user);
        if(CollectionUtil.isNotEmpty(bySql)){
            Resp.fail(Messages.PhoneExist);
        }
        register(request,user,dto);
        return Resp.of(Messages.RegisterSuccess);
    }
    /**
     * 注册
     * @param user
     * @param dto
     */
    private void register(HttpServletRequest request,User user,ForgetPwDto dto){
        user.setPhone(dto.getPhone());
        user.setUsername(dto.getPhone());
        user.setPassword(StringUtil.md5Hex(user.getUsername() + "$" + dto.getPassword()));
        user.setEnable(true);
        dao.save(user);
        if(StrUtil.isNotBlank(dto.getOpenId())){
            //缓存拿wxinfo
            WxUser wxUser = (WxUser) request.getSession().getAttribute(WXINFO_SESSIONKEY);
            if (wxUser != null){
                wxUser.setUserId(user.getId());
                dao.save(wxUser);
            } else {
                wxUser = new WxUser();
                wxUser.setOpenid(dto.getOpenId());
                wxUser.setUserId(user.getId());
                wxUser.setUnionId(dto.getUnionId());
                dao.save(wxUser);
            }
        }
    }
    @Operation(summary = "发送忘记密码短信")
    @GetMapping("/sms/sendSmsForForget")
    public Resp sendSmsForForget(HttpServletRequest request,@RequestParam String phoneNumber,@RequestParam String verify,@CookieValue(value=Captcha.captchaCookieName,required = false) String vid) {
        validCapcha(verify,vid);
        boolean mobile = Validator.isMobile(phoneNumber);
        if (!mobile) {
            Resp.fail(Messages.PhoneIncorrect);
        }
        validIsSend(request,FORGOT_SESSIONKEY,phoneNumber);
        //如果不自动注册，则要验证手机号是否存在
        if (!LoginConfig.FORGET_PW_AUTOREGISTER.value().asBoolean()){
            UserInfo user = new UserInfo();
            user.setUsername(phoneNumber);
            user = dao.loadBySql("select * from t_user where username=:username",user);
            if(ObjectUtil.isEmpty(user)){
                Resp.fail(Messages.PhoneNoExist);
            }
        }
        String templateCode = SmsConfigs.forgetTemplate.strValue();
        String sign = SmsConfigs.captchaSin.strValue();
        String randomNumber = RandomUtil.randomNumbers(6);
        Map<String,Object> params = new HashMap<>();
        params.put("code",randomNumber);
        smsService.sendMessage(sign,phoneNumber,templateCode,params);
        //缓存
        setCache(request,randomNumber,FORGOT_SESSIONKEY,phoneNumber);
        return Resp.ok();
    }
    @Operation(summary = "修改用户密码")
    @PostMapping("changePassword")
    public Resp changePassword(HttpServletRequest request,@RequestBody ForgetPwDto dto) {
        validCode(request,FORGOT_SESSIONKEY,dto.getPhone(),dto.getCode());
        if (ObjectUtil.isEmpty(dto)|| StringUtil.isEmpty(dto.getPassword())) {
            Resp.fail(Messages.PasswordNull);
        }
        if (!Objects.equals(dto.getConfirmPassword(), dto.getPassword())) {
            Resp.fail(Messages.PasswordInconsistent);
        }
        PasswordUtil.checkWeakPassword(dto.getPassword());
        User user = new User();
        user.setUsername(dto.getPhone());
        user = dao.loadBySql("select * from t_user where username=:username", user);
        if (user == null || user.getId() == null){
            if (!LoginConfig.FORGET_PW_AUTOREGISTER.value().asBoolean()){
                Resp.fail(Messages.PhoneNoExist);
            } else {
                //不存在则自动注册
                user = new User();
                register(request,user,dto);
            }
        } else {
            user.setPassword(StringUtil.md5Hex(user.getUsername() + "$" + dto.getPassword()));
            dao.update(user);
        }
        return Resp.of(Messages.OperationSuccess);
    }
    @Operation(summary = "发送微信绑定短信")
    @GetMapping("/sms/sendSmsForWxBind")
    public Resp sendSmsForWxBind(HttpServletRequest request,@RequestParam String phoneNumber,@RequestParam String verify,@CookieValue(value=Captcha.captchaCookieName,required = false) String vid) {
        validCapcha(verify,vid);
        boolean mobile = Validator.isMobile(phoneNumber);
        if (!mobile) {
            Resp.fail(Messages.PhoneIncorrect);
        }
        validIsSend(request,WXBIND_SESSIONKEY,phoneNumber);
        String templateCode = SmsConfigs.captchaTemplate.strValue();
        String sign = SmsConfigs.captchaSin.strValue();
        String randomNumber = RandomUtil.randomNumbers(6);
        Map<String,Object> params = new HashMap<>();
        params.put("code",randomNumber);
        smsService.sendMessage(sign,phoneNumber,templateCode,params);
        //缓存
        setCache(request,randomNumber,WXBIND_SESSIONKEY,phoneNumber);
        return Resp.ok();
    }
    @Operation(summary = "微信绑定用户")
    @PostMapping("wxBind")
    public Resp wxBind(HttpServletRequest request,@RequestBody WxBindDto dto) {
        validCode(request,WXBIND_SESSIONKEY,dto.getPhone(),dto.getCode());
        User user = new User();
        user.setUsername(dto.getPhone());
        user = dao.loadBySql("select * from t_user where username=:username", user);
        String respStr = "";
        String password = "";
        if (user == null || user.getId() == null){
            password = PasswordUtil.generatePassword(8);
            respStr = String.format(Messages.WxRegisterSuccess.toStr(),password);
            ForgetPwDto forgetPwDto = new ForgetPwDto();
            forgetPwDto.setPhone(dto.getPhone());
            forgetPwDto.setPassword(password);//生成随机密码
            forgetPwDto.setOpenId(dto.getOpenId());
            forgetPwDto.setUnionId(dto.getUnionId());
            //不存在则自动注册
            user = new User();
            register(request,user,forgetPwDto);
        } else {
            dao.update(user);
            //缓存拿wxinfo
            WxUser wxUser = (WxUser) request.getSession().getAttribute(WXINFO_SESSIONKEY);
            if (wxUser != null){
                wxUser.setUserId(user.getId());
                dao.save(wxUser);
            } else {
                wxUser = new WxUser();
                wxUser.setOpenid(dto.getOpenId());
                wxUser.setUserId(user.getId());
                wxUser.setUnionId(dto.getUnionId());
                dao.save(wxUser);
            }
        }
        UserInfo userInfo = userService.loadUserInfo(dto.getPhone(),null);
        Current.login(userInfo);
        return Resp.map()
                .set("token", userInfo.getToken())
                .set("un",userInfo.getUserType()+userInfo.getUsername())
                .set("uk", userInfo.getPassword() + TokenUtil.getGlobalSalt())
                .set("message",respStr.isEmpty() ? Messages.OperationSuccess.toStr() : respStr);
    }
    @Operation(summary = "发送登录短信")
    @GetMapping("/sms/sendSmsForLogin")
    public Resp sendSmsForLogin(HttpServletRequest request,@RequestParam String phoneNumber,@RequestParam String verify,@CookieValue(value=Captcha.captchaCookieName,required = false) String vid) {
        validCapcha(verify,vid);
        boolean mobile = Validator.isMobile(phoneNumber);
        if (!mobile) {
            Resp.fail("手机号码格式有误!");
        }
        validIsSend(request,LOGIN_SESSIONKEY,phoneNumber);
        UserInfo user = new UserInfo();
        user.setUsername(phoneNumber);
        user = dao.loadBySql("select * from t_user where username=:username",user);
        if(ObjectUtil.isEmpty(user)){
            Resp.fail("手机号码不存在");
        }
        String templateCode = SmsConfigs.captchaTemplate.strValue();
        String sign = SmsConfigs.captchaSin.strValue();
        String randomNumber = RandomUtil.randomNumbers(6);
        Map<String,Object> params = new HashMap<>();
        params.put("code",randomNumber);
        smsService.sendMessage(sign, phoneNumber, templateCode, params);
        //缓存
        setCache(request,randomNumber,LOGIN_SESSIONKEY,phoneNumber);
        return Resp.ok();
    }
    @Operation(summary = "短信登录")
    @OperationLog("用户短信登录")
    @PostMapping(value="loginByPhone")
    public Resp loginByPhone(HttpServletRequest request,@Valid @RequestBody PhoneLoginDto dto) {
        validCode(request,LOGIN_SESSIONKEY,dto.getPhone(),dto.getCode());
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername(dto.getPhone());
        userInfo = dao.loadBySql("select * from t_user where username=:username",userInfo);
        if(ObjectUtil.isEmpty(userInfo)){
            Resp.fail(SystemController.Messages.LoginError);
        }
        userInfo = userService.loadUserInfo(dto.getPhone(),null);
        Current.login(userInfo);
        return Resp.map()
                .set("token", userInfo.getToken())
                .set("un",userInfo.getUserType()+userInfo.getUsername())
                .set("uk", userInfo.getPassword() + TokenUtil.getGlobalSalt());
        //return Resp.ok();
    }
    /**
     * 验证码验证
     * @param verify
     * @param vid
     */
    private void validCapcha(String verify, String vid){
        if (!Captcha.verify((ServletRequestAttributes) RequestContextHolder.getRequestAttributes(),verify,vid)) {
            Resp.fail(SystemController.Messages.CaptchaError);
        }
    }
    private void validCode(HttpServletRequest request,String sessionKey,String phoneNumber,String code){
        if (request.getSession().getAttribute(sessionKey) == null){
            Resp.fail(Messages.UnknownError);
        }
        //在有效期内
        if (Long.parseLong(request.getSession().getAttribute(sessionKey)+"") > new Date().getTime()){
            //手机号码是否与发送短信的号码一致
            if (request.getSession().getAttribute(sessionKey+"_Phone") != null
                    && !request.getSession().getAttribute(sessionKey+"_Phone").equals(phoneNumber)){
                Resp.fail(Messages.PhoneWorry);
            }
            //验证码对不对
            if (request.getSession().getAttribute(sessionKey+"_Code") != null
                    && !request.getSession().getAttribute(sessionKey+"_Code").equals(code)){
                Resp.fail(Messages.CodeError);
            }
            //比对通过，清除session
            request.getSession().setAttribute(sessionKey,null);
            request.getSession().setAttribute(sessionKey+"_Phone",null);
            request.getSession().setAttribute(sessionKey+"_Code",null);
        } else {
            Resp.fail(Messages.CodeInvalid);
        }
    }
    //防止重复点击
    private void validIsSend(HttpServletRequest request,String sessionKey,String phoneNumber){
        if (request.getSession() == null){
            return;
        }
        if (request.getSession().getAttribute(sessionKey) == null){
            return;
        }
        if (!LockStatusBL.interval_over_5s(request)){
            Resp.fail(Messages.OperationLimit);
            return;
        }
        LockStatusBL.addQueue(request);
        if (request.getSession() == null){
            Resp.fail(Messages.UnknownError);
        }
        //在有效期内,同样的手机号码
        if (Long.parseLong(request.getSession().getAttribute(sessionKey)+"") > new Date().getTime()
         && request.getSession().getAttribute(sessionKey+"_Phone").equals(phoneNumber)) {
             Resp.fail(Messages.CodeTimeLimit);
        }
    }
    /**
     * 缓存
     * @param request
     * @param randomNumber
     * @param sessionKey
     * @param phoneNumber
     */
    private void setCache(HttpServletRequest request,String randomNumber,String sessionKey,String phoneNumber){
        request.getSession().setAttribute(sessionKey,new Date().getTime()+ExpireTime);
        request.getSession().setAttribute(sessionKey+"_Phone",phoneNumber);
        request.getSession().setAttribute(sessionKey+"_Code",randomNumber);
    }
    @Data
    public static class ForgetPwDto{
        @NotBlank(message = "密码不能为空")
        private String confirmPassword;
        @NotBlank(message = "密码不能为空")
        private String password;
        @NotBlank(message = "手机号不能为空")
        private String phone;
        @NotBlank(message = "验证码不能为空")
        private String code;
        private String openId;
        private String unionId;
        private String bindWx;
    }
    @Data
    public static class WxBindDto{
        @NotBlank(message = "手机号不能为空")
        private String phone;
        @NotBlank(message = "验证码不能为空")
        private String code;
        @NotBlank(message = "openId不能为空")
        private String openId;
        @NotBlank(message = "unionId不能为空")
        private String unionId;
        private String bindWx;
    }
    @Data
    public static class PhoneLoginDto{
        @NotBlank(message = "手机号不能为空")
        private String phone;
        @NotBlank(message = "验证码不能为空")
        private String code;
    }
    @Data
    public static class LoginConfigDto{
        private Boolean signPw;
        private Boolean signSms;
        private Boolean forgetPw;
        private Boolean register;
        private Boolean loginWx;
        private String appId;
    }
    @AllArgsConstructor
    @Getter
    enum Messages implements LocalizedMessage{
        PhoneIncorrect("The format of the phone number is incorrect!","手机号码格式有误!"),
        PhoneWorry("Inconsistent with the phone number used to send the text message","与发送短信的手机号码不一致!"),
        PhoneExist("The phone number already exists and cannot be registered","手机号码已存在，不可注册"),
        PhoneNoExist("The phone number does not exist","手机号码不存在"),
        CaptchaError("captcha error","验证码错误"),
        CodeError("code error","短信验证码错误"),
        CodeInvalid("code invalid","短信验证码失效"),
        CodeErrorOrExpired("SMS Verification code does not exist or has expired","短信验证码不存在或已失效"),
        OperationLimit("The operation is too frequent, please wait for 5 seconds before proceeding","操作太频繁，请5秒后再操作"),
        CodeTimeLimit("The same phone number can only send one text message within one minute","同一个手机号码一分钟内只能发送一条短信"),
        CodeSend("The SMS has been sent and is valid for "+ExpireMINUTE+" minutes","短信已发送，有效时间"+ExpireMINUTE+"分钟"),
        CodeNull("SMS verification code cannot be empty","短信验证码不能为空"),
        LoginError("username or password error","用户名或密码错误"),
        PasswordNull("Password cannot be empty","密码不能为空"),
        PasswordNotSafe("the current password is not secure, please change the password","当前密码不安全，请修改密码"),
        PasswordInconsistent("two passwords are inconsistent","两次密码不一致"),
        UnknownError("unknown error","未知错误"),
        RegisterSuccess("registered successfully","注册成功"),
        OperationSuccess("Operation successful","操作成功"),
        WxRegisterSuccess("Account registration successful, account password is:%s","账号注成功，账号密码为：%s");
        private final String en;
        private final String zh;
    }
}
